/*
 * XInput1_3-dev - Replacement of xinput library to help with creating
 *                 drivers/debugging application
 *
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */

#include "stdafx.h"

#include "DeviceInfo.h"
#include "DriverComm.h"
#include "Protocol.h"
#include "Utilities.h"

static FnDeviceIoControl_t g_pfnDeviceIoControl = NULL;
static XINPUT_CAPABILITIES GlobalCapabilities = { XINPUT_DEVTYPE_GAMEPAD, XINPUT_DEVSUBTYPE_GAMEPAD, XINPUT_CAPS_VOICE_SUPPORTED,
	0xF3FF, 01, 01, 0001, 0001, 0001, 0001,
	0001, 0001 };

GamepadState0100 *CopyGamepadStateToDeviceInfo(PDEVICE_INFO pDeviceInfo,GamepadState0100 * pGamepadState)
{
	pDeviceInfo->State.dwPacketNumber = pGamepadState->dwPacketNumber;
	pDeviceInfo->State.Gamepad.bLeftTrigger = pGamepadState->Gamepad.bLeftTrigger;
	pDeviceInfo->State.Gamepad.bRightTrigger = pGamepadState->Gamepad.bRightTrigger;
	pDeviceInfo->State.Gamepad.sThumbLX = pGamepadState->Gamepad.sThumbLX;
	pDeviceInfo->State.Gamepad.sThumbLY = pGamepadState->Gamepad.sThumbLY;
	pDeviceInfo->State.Gamepad.sThumbRX = pGamepadState->Gamepad.sThumbRX;
	pDeviceInfo->State.Gamepad.sThumbRY = pGamepadState->Gamepad.sThumbRY;
	pDeviceInfo->State.Gamepad.wButtons = pGamepadState->Gamepad.wButtons;

	if ( pGamepadState->bEnabled == TRUE )
		pDeviceInfo->dwFlags |= 1u;
	else
		pDeviceInfo->dwFlags &= ~1;

	pDeviceInfo->Dummy82 = pGamepadState->Dummy2;
	return pGamepadState;
}

GamepadState0101 *CopyGamepadStateToDeviceInfo(PDEVICE_INFO pDeviceInfo,GamepadState0101 *pGamepadState)
{
	pDeviceInfo->State.dwPacketNumber = pGamepadState->dwPacketNumber;
	pDeviceInfo->State.Gamepad.bLeftTrigger = pGamepadState->Gamepad.bLeftTrigger;
	pDeviceInfo->State.Gamepad.bRightTrigger = pGamepadState->Gamepad.bRightTrigger;
	pDeviceInfo->State.Gamepad.sThumbLX = pGamepadState->Gamepad.sThumbLX;
	pDeviceInfo->State.Gamepad.sThumbLY = pGamepadState->Gamepad.sThumbLY;
	pDeviceInfo->State.Gamepad.sThumbRX = pGamepadState->Gamepad.sThumbRX;
	pDeviceInfo->State.Gamepad.sThumbRY = pGamepadState->Gamepad.sThumbRY;
	pDeviceInfo->State.Gamepad.wButtons = (pGamepadState->Gamepad.wButtons & 0xF7FF);
	//pDeviceInfo->State.Gamepad.wButtons &= 0xF7FF;

	if ( pGamepadState->bEnabled == TRUE )
		pDeviceInfo->dwFlags |= 1u;
	else
		pDeviceInfo->dwFlags &= ~1;

	pDeviceInfo->Dummy82 = pGamepadState->Dummy4;
	return pGamepadState;
}

PXINPUT_CAPABILITIES TranslateCapabilities(GamepadCapabilities0101 const *pGPCapabilities, XINPUT_CAPABILITIES *pXICapabilities)
{
	pXICapabilities->Type = pGPCapabilities->Type;
	pXICapabilities->SubType = pGPCapabilities->SubType;
	pXICapabilities->Flags = 4;

	pXICapabilities->Gamepad.wButtons = pGPCapabilities->wButtons;
	pXICapabilities->Gamepad.bLeftTrigger = pGPCapabilities->bLeftTrigger;
	pXICapabilities->Gamepad.bRightTrigger = pGPCapabilities->bRightTrigger;
	pXICapabilities->Gamepad.sThumbLX = pGPCapabilities->sThumbLX;
	pXICapabilities->Gamepad.sThumbLY = pGPCapabilities->sThumbLY;
	pXICapabilities->Gamepad.sThumbRX = pGPCapabilities->sThumbRX;
	pXICapabilities->Gamepad.sThumbRY = pGPCapabilities->sThumbRY;

	pXICapabilities->Vibration.wLeftMotorSpeed = pGPCapabilities->bLeftMotorSpeed;
	pXICapabilities->Vibration.wRightMotorSpeed = pGPCapabilities->bRightMotorSpeed;

	pXICapabilities->Gamepad.wButtons &= 0xF7FF;

	return pXICapabilities;
}
HRESULT SendReceiveIoctl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPOVERLAPPED lpOverlapped)
{
	BOOL bResult = 0;
	DWORD BytesReturned = 0;

	if ( g_pfnDeviceIoControl )
		bResult = g_pfnDeviceIoControl( hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, &BytesReturned, lpOverlapped);
	else
		bResult = DeviceIoControl( hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, &BytesReturned, lpOverlapped);

	if ( bResult )
		return S_OK;

	if ( GetLastError() == ERROR_IO_PENDING && lpOverlapped )
      return E_PENDING;

	return E_FAIL;
}

HRESULT SendIoctl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize)
{
	return SendReceiveIoctl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, 0, 0, 0);
}

HRESULT ReceiveIoctl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpOutBuffer, DWORD nOutBufferSize)
{
	return SendReceiveIoctl(hDevice, dwIoControlCode, 0, 0, lpOutBuffer, nOutBufferSize, 0);
}

HRESULT DriverComm::CancelGuideButtonWait(PDEVICE_INFO pDeviceInfo)
{
	if ( pDeviceInfo->hWaitDevice != INVALID_HANDLE_VALUE )
	{
		CloseHandle(pDeviceInfo->hWaitDevice);
		pDeviceInfo->hWaitDevice = INVALID_HANDLE_VALUE;
	}
	return S_OK;
}

BOOL DriverComm::CheckForDriverHook(ulong,void *)
{
	// TODO: Implement
	return TRUE;
}

#pragma pack(push, 1)
struct IOCTLParameter
{
	WORD wUnknown;
	BYTE bPort;
};
#pragma pack(pop)

HRESULT DriverComm::GetAudioDeviceInformation(PDEVICE_INFO pDeviceInfo, DriverComm::XINPUT_AUDIO_INFORMATION *)
{
	// TODO: Implement
	/*if ( pDeviceInfo->wVersion >= 258 )
	{
		IOCTLParameter InBuffer;
		InBuffer.wUnknown = 258;
		InBuffer.bPort = pDeviceInfo->bPort;

		HRESULT hResult = SendReceiveIoctl( pDeviceInfo->hDevice, Protocol::IOCTL_XINPUT_GET_AUDIO_INFORMATION, &InBuffer, 3, &OutBuffer, 7, 0);
		if ( (hResult & 0x80000000u) == 0 )
		{
			//*(_WORD *)a2 = v7;
			//*(_WORD *)(a2 + 2) = v8;
			//*(_BYTE *)(a2 + 4) = v9;
		}

		return hResult;
	}*/
  
	return E_FAIL;
}

struct BatteryIOCTLParameter
{
	WORD wUnknown;
	BYTE bPort;
	BYTE bUnknown;
};

HRESULT DriverComm::GetBatteryInformation(PDEVICE_INFO pDeviceInfo, uchar uUnknown,PXINPUT_BATTERY_INFORMATION pBatteryInformation)
{
	if ( pDeviceInfo->wVersion >= 258 )
	{
		BatteryIOCTLParameter InBuffer;
		InBuffer.wUnknown = 258;
		InBuffer.bPort = (BYTE)pDeviceInfo->bPort;
		InBuffer.bUnknown = uUnknown;
    
		int OutBuffer = 0;

		HRESULT hResult = SendReceiveIoctl(pDeviceInfo->hDevice, Protocol::IOCTL_XINPUT_GET_BATTERY_INFORMATION, &InBuffer, 4, &OutBuffer, 4, 0);
		if ( (hResult & 0x80000000u) == 0 )
		{
			WORD wValues = HIWORD(OutBuffer);
			hResult = Utilities::SafeCopyToUntrustedBuffer(pBatteryInformation, &wValues, 2u);
		}

		return hResult;
	}

	// TODO: Implement getting headset battery status
  /*else
  {
    v10 = 1;
    v11 = 3;
    if ( a2 == 1 )
    {
      v6 = &v5;
      v7 = &v8;
      v9 = DeviceInfo::g_pfnGetAudioDeviceGuidsDispatcher(a1, &v6, 2);
      if ( v9 < 0 || __(&v5, dword_4012F0) )
      {
        v10 = 0;
        v11 = 0;
      }
    }
    result = Utilities::SafeCopyToUntrustedBuffer(a3, &v10, 2u);
    v12 = result;
  }*/
	return E_FAIL;
}

HRESULT DriverComm::GetCapabilities(PDEVICE_INFO pDeviceInfo, XINPUT_CAPABILITIES *pCapabilities)
{
	if ( pDeviceInfo->wVersion == 256 )
	{
		return Utilities::SafeCopyToUntrustedBuffer(pCapabilities, &GlobalCapabilities, sizeof(XINPUT_CAPABILITIES));
	}

	IOCTLParameter InBuffer;
	InBuffer.wUnknown = 257;
	InBuffer.bPort = (BYTE)pDeviceInfo->bPort;

	GamepadCapabilities0101 OutBuffer = {0};

	HRESULT hResult = SendReceiveIoctl(pDeviceInfo->hDevice, Protocol::IOCTL_XINPUT_GET_CAPABILITIES, &InBuffer, 3, &OutBuffer, sizeof(GamepadCapabilities0101), 0);
	if ( (hResult & 0x80000000) == 0 )
		TranslateCapabilities(&OutBuffer, pCapabilities);

	return hResult;
}


HRESULT DriverComm::GetDeviceInfoFromInterface(HANDLE hDevice, PINTERFACE_INFORMATION pInterfaceInformation)
{
	HRESULT hResult = ReceiveIoctl(hDevice, Protocol::IOCTL_XINPUT_GET_INFORMATION, pInterfaceInformation, 0xCu);
	if ( (hResult & 0x80000000) == 0 )
		return S_OK;
	
	return hResult;
}

HRESULT DriverComm::GetLEDState(PDEVICE_INFO pDeviceInfo, uchar *pbLEDState)
{
	
	*pbLEDState = Protocol::LEDState::XINPUT_LED_OFF;
	if ( pDeviceInfo->wVersion >= 257 )
	{
		IOCTLParameter InBuffer;
		InBuffer.wUnknown = 257;
		InBuffer.bPort = (BYTE)pDeviceInfo->bPort;

		BYTE OutBuffer[3];

		HRESULT hResult = SendReceiveIoctl(pDeviceInfo->hDevice, Protocol::IOCTL_XINPUT_GET_LED_STATE, &InBuffer, 3, &OutBuffer, 3, 0);
		if ( (hResult & 0x80000000u) == 0 )
			*pbLEDState = OutBuffer[2];
		
		return hResult;
	}
	
	return S_OK;
}
HRESULT DriverComm::GetLatestDeviceInfo(PDEVICE_INFO pDeviceInfo)
{
	LPVOID lpInBuffer = NULL;
	DWORD nInBufferSize = 0;

	LPVOID lpOutBuffer = NULL;
	DWORD nOutBufferSize = 0;

	IOCTLParameter IOCTLParam;

	GamepadState0100 State0100;
	GamepadState0101 State0101;

	if ( pDeviceInfo->wVersion == 256 )
	{
		lpInBuffer = &pDeviceInfo->bPort;
		nInBufferSize = 1;
		lpOutBuffer = &State0100;
		nOutBufferSize = sizeof(GamepadState0100);
	}
	else
	{
		IOCTLParam.wUnknown = 257;
		IOCTLParam.bPort = (BYTE)pDeviceInfo->bPort;
		lpInBuffer = &IOCTLParam;
		nInBufferSize = sizeof(IOCTLParameter);
		lpOutBuffer = &State0101;
		nOutBufferSize = sizeof(GamepadState0101);
	}

	memset(lpOutBuffer, 0, nOutBufferSize);

	HRESULT hResult = SendReceiveIoctl(pDeviceInfo->hDevice, Protocol::IOCTL_XINPUT_GET_GAMEPAD_STATE, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, 0);
	if ( (hResult & 0x80000000u) == 0 )
	{
		if ( pDeviceInfo->wVersion == 256 )
			CopyGamepadStateToDeviceInfo(pDeviceInfo, &State0100);
		else
			CopyGamepadStateToDeviceInfo(pDeviceInfo, &State0101);
	
		return S_OK;
	}

	return hResult;
}

HRESULT DriverComm::Initialize(void)
{
	g_pfnDeviceIoControl = NULL;
	//g_pfnGetClassDevs = NULL;
	//g_pfnEnumDeviceInterfaces = NULL;
	//g_pfnGetDeviceInterfaceDetail = NULL;
	//g_pfnDestroyDeviceInfoList = NULL;

	return S_OK;
}

HRESULT DriverComm::PowerOffController(PDEVICE_INFO pDeviceInfo)
{
	if ( pDeviceInfo->wVersion >= 258 )
	{
		IOCTLParameter Param;
		Param.wUnknown = 258;
		Param.bPort = (BYTE)pDeviceInfo->bPort;
		return SendIoctl(pDeviceInfo->hDevice, Protocol::IOCTL_XINPUT_POWER_DOWN_DEVICE, &Param, 3);
	}
	
	return E_FAIL;
}

#pragma pack(push, 1)
struct IOCTLSetState
{
	BYTE bPort;
	BYTE bLEDState;
	BYTE bLeftMotorSpeed;
	BYTE bRightMotorSpeed;
	BYTE bOpCode;
};
#pragma pack(pop)

HRESULT DriverComm::SendDeviceVibration(PDEVICE_INFO pDeviceInfo)
{
	IOCTLSetState ioctl;
	ioctl.bPort = (BYTE)pDeviceInfo->bPort;
	ioctl.bLEDState = 0;
	ioctl.bOpCode = 2;
	ioctl.bLeftMotorSpeed = (BYTE)(pDeviceInfo->Vibration.wLeftMotorSpeed >> 8);
	ioctl.bRightMotorSpeed = (BYTE)(pDeviceInfo->Vibration.wRightMotorSpeed >> 8);

	return SendIoctl(pDeviceInfo->hDevice, Protocol::IOCTL_XINPUT_SET_GAMEPAD_STATE, &ioctl, 5);
}

HRESULT DriverComm::SendLEDState(PDEVICE_INFO pDeviceInfo,BYTE bLEDState)
{
	IOCTLSetState ioctl;
	ioctl.bPort = (BYTE)pDeviceInfo->bPort;
	ioctl.bLeftMotorSpeed = 0;
	ioctl.bRightMotorSpeed = 0;
	ioctl.bOpCode = 1;
	ioctl.bLEDState = bLEDState;

	return SendIoctl(pDeviceInfo->hDevice, Protocol::IOCTL_XINPUT_SET_GAMEPAD_STATE, &ioctl, 5);
}

HRESULT DriverComm::WaitForGuideButton(HANDLE hDevice, BYTE bPort, PXINPUT_LISTEN_STATE pListenState)
{
	HANDLE hEvent = CreateEventW(0, 1, 0, 0);
	if ( hEvent )
	{
		OVERLAPPED Overlapped;
		Overlapped.Internal = 0;
		Overlapped.InternalHigh = 0;
		Overlapped.Offset = 0;
		Overlapped.OffsetHigh = 0;
		Overlapped.hEvent = hEvent;


		IOCTLParameter ioctl;
		ioctl.wUnknown = 258;
		ioctl.bPort = bPort;

		GamepadState0101 State;
		memset(&State, 0, sizeof(GamepadState0101));

		LPVOID lpOutBuffer = &State;
		DWORD nOutBufferSize = sizeof(GamepadState0101);

		State.bEnabled = 5;
		HRESULT hResult = SendReceiveIoctl(hDevice, Protocol::IOCTL_XINPUT_WAIT_FOR_GUIDE_BUTTON, &ioctl, 3, lpOutBuffer, nOutBufferSize, &Overlapped);

		if ( (hResult & 0x80000000) == 0 || hResult == E_PENDING )
		{
			pListenState->dwErrorCode = ERROR_IO_PENDING;
			DWORD NumberOfBytesTransferred = 0;

			if ( GetOverlappedResult(hDevice, &Overlapped, &NumberOfBytesTransferred, 1) )
			{
				if ( NumberOfBytesTransferred == nOutBufferSize )
				{
					if ( State.bEnabled == 1 )
					{
						pListenState->dwErrorCode = 0;
						pListenState->State.dwPacketNumber = State.dwPacketNumber;
						pListenState->State.Gamepad.bLeftTrigger = State.Gamepad.bLeftTrigger;
						pListenState->State.Gamepad.bRightTrigger = State.Gamepad.bRightTrigger;
						pListenState->State.Gamepad.sThumbLX = State.Gamepad.sThumbLX;
						pListenState->State.Gamepad.sThumbLY = State.Gamepad.sThumbLY;
						pListenState->State.Gamepad.sThumbRX = State.Gamepad.sThumbRX;
						pListenState->State.Gamepad.sThumbRY = State.Gamepad.sThumbRY;
						pListenState->State.Gamepad.wButtons = State.Gamepad.wButtons;
					}
					else
					{
						pListenState->dwErrorCode = ERROR_DEVICE_NOT_CONNECTED;
					}
				}
				else
				{
					pListenState->dwErrorCode = ERROR_CANCELLED;
				}
			}
			else
			{
				if ( GetLastError() == ERROR_OPERATION_ABORTED )
					pListenState->dwErrorCode = ERROR_CANCELLED;
				else
					pListenState->dwErrorCode = GetLastError();
			}

			CloseHandle(hEvent);
			return S_OK;
		}
		else
		{
			CloseHandle(hEvent);
			pListenState->dwErrorCode = GetLastError();
			return hResult;
		}
	}
	
	return E_FAIL;
}

BOOL DriverComm::SetupDiWrapper::DestroyDeviceInfoList(HDEVINFO DeviceInfoSet)
{
	//if ( g_pfnDestroyDeviceInfoList )
	//	return g_pfnDestroyDeviceInfoList(DeviceInfoSet);
	
	return SetupDiDestroyDeviceInfoList(DeviceInfoSet);
}

BOOL DriverComm::SetupDiWrapper::EnumDeviceInterfaces(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, const GUID *InterfaceClassGuid, DWORD MemberIndex, PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData)
{
	/*if ( g_pfnEnumDeviceInterfaces )
		return g_pfnEnumDeviceInterfaces(
			DeviceInfoSet,
			DeviceInfoData,
			InterfaceClassGuid,
			MemberIndex,
			DeviceInterfaceData);*/

	return SetupDiEnumDeviceInterfaces(
		DeviceInfoSet,
		DeviceInfoData,
		InterfaceClassGuid,
		MemberIndex,
		DeviceInterfaceData);
}

HDEVINFO DriverComm::SetupDiWrapper::GetClassDevs(const GUID *ClassGuid, PCWSTR Enumerator, HWND hwndParent, DWORD Flags)
{
	/*if( g_pfnGetClassDevs )
		return g_pfnGetClassDevs(ClassGuid, Enumerator, hwndParent, Flags);*/

	return SetupDiGetClassDevsW(ClassGuid, Enumerator, hwndParent, Flags);
}

BOOL DriverComm::SetupDiWrapper::GetDeviceInterfaceDetail(HDEVINFO DeviceInfoSet, PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, PSP_DEVICE_INTERFACE_DETAIL_DATA_W DeviceInterfaceDetailData, DWORD DeviceInterfaceDetailDataSize, PDWORD RequiredSize, PSP_DEVINFO_DATA DeviceInfoData)
{
	/*if ( g_pfnGetDeviceInterfaceDetail )
		return g_pfnGetDeviceInterfaceDetail(
			DeviceInfoSet,
			DeviceInterfaceData,
			DeviceInterfaceDetailData,
			DeviceInterfaceDetailDataSize,
			RequiredSize,
			DeviceInfoData);*/

	return SetupDiGetDeviceInterfaceDetailW(
		DeviceInfoSet,
		DeviceInterfaceData,
		DeviceInterfaceDetailData,
		DeviceInterfaceDetailDataSize,
		RequiredSize,
		DeviceInfoData);
}